package com.managertrade.util.strategy;

import com.managertrade.dto.vo.KlineWithIndicators;

import java.util.List;
import java.util.Optional;

/**
 * 交易K线反转信号检测器
 * 支持检测底部反转（上涨信号）和顶部反转（下跌信号）
 * 提供置信评分系统，用于衡量反转信号强度
 */
public class TrendSignalDetector {
    /**
     * 封装反转信号结果，包含上涨与下跌信号布尔判断及评分
     */
    public static class ReversalSignal {
        // 上涨信号指标
        public boolean longLowerShadow;                // 长下影线：表明下方买盘强
        public boolean consecutiveBottoms;             // 连续底部止跌：多根K线收盘不再下跌
        public boolean higherLows;                     // 低点逐渐抬高：短期止跌回升
        public boolean growingSmallBull;               // 小阳线放量：阳线实体增大，量能增加
        public boolean breakoutFromConsolidation;      // 突破震荡底部：收盘上破布林中轨
        public boolean emaCross;                       // EMA 金叉：EMA7 上穿 EMA21
        public boolean backToBoll;                     // 回到布林带中轨或上轨
        public boolean macdRisingFromNegative;         // MACD 由负转正：趋势反转信号
        public boolean volumeDriedThenGrew;            // 成交量缩后放：释放恐慌盘后主力吸筹

        // 下跌信号指标
        public boolean longUpperShadow;                // 长上影线：上方抛压大
        public boolean consecutiveTops;                // 连续顶部止涨：K线高位横盘或回落
        public boolean lowerHighs;                     // 高点逐渐降低：上涨动能减弱
        public boolean growingSmallBear;               // 小阴线放量：实体增大，量能增加
        public boolean breakdownFromConsolidation;     // 跌破震荡区：布林中轨下破
        public boolean emaDeathCross;                  // EMA 死叉：EMA7 下穿 EMA21
        public boolean fallBelowBoll;                  // 跌破布林中轨
        public boolean macdFallingFromPositive;        // MACD 由正转负：顶部回落信号
        public boolean volumeSurgeThenDrop;            // 放量后缩：顶部出货迹象

        /**
         * 是否为底部反转信号（评分 >= 0.7）
         */
        public boolean isBottomReversal() {
            return scoreBottomReversal() >= 0.7;
        }

        /**
         * 是否为顶部反转信号（评分 >= 0.7）
         */
        public boolean isTopReversal() {
            return scoreTopReversal() >= 0.7;
        }

        /**
         * 计算底部反转置信评分（范围：0 ~ 1）
         * 权重：重要信号分值更高，例如长下影线、金叉、MACD 等
         */
        public double scoreBottomReversal() {
            double score = 0;
            score += longLowerShadow ? 2 : 0;
            score += consecutiveBottoms ? 1 : 0;
            score += higherLows ? 1.5 : 0;
            score += growingSmallBull ? 1 : 0;
            score += breakoutFromConsolidation ? 1 : 0;
            score += emaCross ? 1.5 : 0;
            score += backToBoll ? 0.5 : 0;
            score += macdRisingFromNegative ? 1.5 : 0;
            score += volumeDriedThenGrew ? 1 : 0;
            return score / 10.0;
        }

        /**
         * 计算顶部反转置信评分（范围：0 ~ 1）
         * 权重同上，信号分值根据重要程度分配
         */
        public double scoreTopReversal() {
            double score = 0;
            score += longUpperShadow ? 2 : 0;
            score += consecutiveTops ? 1 : 0;
            score += lowerHighs ? 1.5 : 0;
            score += growingSmallBear ? 1 : 0;
            score += breakdownFromConsolidation ? 1 : 0;
            score += emaDeathCross ? 1.5 : 0;
            score += fallBelowBoll ? 0.5 : 0;
            score += macdFallingFromPositive ? 1.5 : 0;
            score += volumeSurgeThenDrop ? 1 : 0;
            return score / 10.0;
        }
    }

    /**
     * 传入 K 线列表，检测并计算反转信号
     * 建议传入最近 5~10 根 K 线数据
     *
     *      * 传入 K 线列表，检测并计算反转信号
     *      * @param list List<KlineWithIndicators> 最近的 K 线数据，建议传入5根以上的连续数据
     *      * @return ReversalSignal 检测到的信号对象，包含上涨/下跌信号的布尔值及评分
     *      *
     *      * 步骤说明：
     *      * 1. 检查数据是否有效（不能为空或少于5根K线）
     *      * 2. 取最后一根和倒数第二根K线作为分析基础（分析当前K线与前一根的关系）
     *      * 3. 分别计算上涨信号（如长下影线、MACD金叉、EMA上穿等）
     *      * 4. 分别计算下跌信号（如长上影线、MACD死叉、EMA下穿等）
     *      * 5. 每个信号结果保存在 ReversalSignal 中
     *      * 6. 用户可调用 isBottomReversal() 或 isTopReversal() 判断信号是否有效
     *      * 7. 也可使用 scoreBottomReversal() 或 scoreTopReversal() 获取置信评分（0~1）
     *
     */
    public static ReversalSignal detect(List<KlineWithIndicators> list) {
        ReversalSignal signal = new ReversalSignal();

        // 1. 校验数据长度是否足够
        if (list == null || list.size() < 5) return signal;

        int size = list.size();
        KlineWithIndicators last = list.get(size - 1);       // 最新一根K线
        KlineWithIndicators prev = list.get(size - 2);       // 倒数第二根K线

        // ========== 上涨信号判断（底部反转）==========

        // 判断长下影线（实体为open/close差，影线为最低价减去实体下边）
        double entity = Math.abs(last.getClose() - last.getOpen());
        double lowerShadow = Math.min(last.getOpen(), last.getClose()) - last.getLow();
        signal.longLowerShadow = lowerShadow > 1.5 * entity;

        // 连续多根K线不再下跌
        signal.consecutiveBottoms = true;
        for (int i = size - 4; i < size; i++) {
            if (i > 0 && list.get(i).getClose() < list.get(i - 1).getClose() * 0.98) {
                signal.consecutiveBottoms = false;
                break;
            }
        }

        // 三根K线低点逐步抬高
        signal.higherLows = list.get(size - 1).getLow() > list.get(size - 2).getLow() &&
                list.get(size - 2).getLow() > list.get(size - 3).getLow();

        // 小阳线逐步放量：本根K线收阳，实体变大，成交量增加
        boolean growingBody = last.getClose() > last.getOpen() && entity > Math.abs(prev.getClose() - prev.getOpen());
        boolean volUp = last.getVolume() > prev.getVolume();
        signal.growingSmallBull = growingBody && volUp;

        // 当前价格是否突破布林中轨（震荡区上边）
        signal.breakoutFromConsolidation = last.getClose() > Optional.ofNullable(last.getBollMiddle()).orElse(0.0);

        // EMA7 上穿 EMA21
        signal.emaCross = Optional.ofNullable(last.getEma7()).orElse(0.0) > Optional.ofNullable(last.getEma21()).orElse(0.0) &&
                Optional.ofNullable(prev.getEma7()).orElse(0.0) <= Optional.ofNullable(prev.getEma21()).orElse(0.0);

        // 价格是否重新站上布林带中轨
        signal.backToBoll = last.getClose() >= Optional.ofNullable(last.getBollMiddle()).orElse(0.0);

        // MACD 从负值转为正值，说明动能反转
        signal.macdRisingFromNegative = Optional.ofNullable(last.getMacd()).orElse(0.0) > 0 &&
                Optional.ofNullable(prev.getMacd()).orElse(0.0) < 0;

        // 成交量先缩后放，表示恐慌释放后出现主力吸筹
        signal.volumeDriedThenGrew = size >= 5 &&
                list.get(size - 4).getVolume() > list.get(size - 3).getVolume() &&
                list.get(size - 3).getVolume() > list.get(size - 2).getVolume() &&
                list.get(size - 1).getVolume() > list.get(size - 2).getVolume();

        // ========== 下跌信号判断（顶部反转）==========

        // 判断长上影线（上影线长度 = 高点 - 实体上边）
        double upperShadow = last.getHigh() - Math.max(last.getOpen(), last.getClose());
        signal.longUpperShadow = upperShadow > 1.5 * entity;

        // 连续顶部：多根K线收盘价不上涨
        signal.consecutiveTops = true;
        for (int i = size - 4; i < size; i++) {
            if (i > 0 && list.get(i).getClose() > list.get(i - 1).getClose() * 1.02) {
                signal.consecutiveTops = false;
                break;
            }
        }

        // 高点逐步降低，表明反弹无力
        signal.lowerHighs = list.get(size - 1).getHigh() < list.get(size - 2).getHigh() &&
                list.get(size - 2).getHigh() < list.get(size - 3).getHigh();

        // 小阴线放量下跌：实体变大，成交量增大
        boolean fallingBody = last.getClose() < last.getOpen() && entity > Math.abs(prev.getClose() - prev.getOpen());
        boolean volDown = last.getVolume() > prev.getVolume();
        signal.growingSmallBear = fallingBody && volDown;

        // 跌破布林中轨，表明价格进入弱势区
        signal.breakdownFromConsolidation = last.getClose() < Optional.ofNullable(last.getBollMiddle()).orElse(Double.MAX_VALUE);

        // EMA 死叉：短期EMA下穿长期EMA
        signal.emaDeathCross = Optional.ofNullable(last.getEma7()).orElse(0.0) < Optional.ofNullable(last.getEma21()).orElse(0.0) &&
                Optional.ofNullable(prev.getEma7()).orElse(0.0) >= Optional.ofNullable(prev.getEma21()).orElse(0.0);

        // 是否跌破布林中轨
        signal.fallBelowBoll = last.getClose() < Optional.ofNullable(last.getBollMiddle()).orElse(Double.MAX_VALUE);

        // MACD 从正值转为负值
        signal.macdFallingFromPositive = Optional.ofNullable(last.getMacd()).orElse(0.0) < 0 &&
                Optional.ofNullable(prev.getMacd()).orElse(0.0) > 0;

        // 成交量先升后降，说明顶部主力出货完成
        signal.volumeSurgeThenDrop = size >= 5 &&
                list.get(size - 4).getVolume() < list.get(size - 3).getVolume() &&
                list.get(size - 3).getVolume() < list.get(size - 2).getVolume() &&
                list.get(size - 1).getVolume() < list.get(size - 2).getVolume();

        return signal;
    }
}

